package com.example.gobang.socket;

import com.example.gobang.constant.Constants;
import com.example.gobang.manager.OnlineUserManager;
import com.example.gobang.manager.RoomManger;
import com.example.gobang.mapper.UserMapper;
import com.example.gobang.model.GameReadyResult;
import com.example.gobang.model.Result;
import com.example.gobang.model.Room;
import com.example.gobang.model.UserInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;

@Component
@Slf4j
public class GameSocket extends TextWebSocketHandler {
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private OnlineUserManager onlineUserManager;
    @Autowired
    private RoomManger roomManger;
    @Autowired
    private UserMapper userMapper;
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 进入游戏房间后
        Result result = new Result();
        // 1. 获取用户身份信息
        UserInfo userInfo = (UserInfo) session.getAttributes().get(Constants.USER);
        // 2. 判断用户是否已经登录
        if(userInfo == null) {
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(Result.noLogin())));
            return;
        }
        // 3. 判断当前用户是否已经进入房间（通过房间管理器查询）
        Room room = roomManger.getRoomByUserId(userInfo.getUserId());
        if(room == null) {
            // 没有找到对应房间，玩家未匹配到
            result.setErrorMessage("用户尚未匹配到对手！");
            result.setStatus(Constants.MATCH_FAIL);
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(result)));
            return;
        }
        // 4. 判断是否多开游戏
        if(onlineUserManager.getFromGameRoom(userInfo.getUserId()) != null
                || onlineUserManager.getFromGameHall(userInfo.getUserId()) != null) {
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(Result.repeatConnection())));
            return;
        }
        // 5. 拿到玩家信息，将玩家设置为在线状态
        onlineUserManager.enterGameRoom(userInfo.getUserId(), session);
        // 6. 将两个玩家加入到游戏房间中，进行加锁操作
        synchronized (room) {
            // 当前房间还没有玩家，当前玩家作为玩家1
            if(room.getUser1() == null) {
                room.setUser1(userInfo);
                // 将玩家1设置为先手
                room.setWhiteUser(userInfo.getUserId());
                log.info("玩家1：{} 已进入游戏房间{}", userInfo.getUserName(), room.getRoomId());
                return;
            }
            // 当前房间已有玩家，当前玩家作为玩家2
            if(room.getUser2() == null) {
                room.setUser2(userInfo);
                log.info("玩家2：{} 已进入游戏房间{}", userInfo.getUserName(), room.getRoomId());
                // 通知玩家，游戏开始
                noticeGameReady(room, room.getUser1(), room.getUser2()); // 通知玩家1
                noticeGameReady(room, room.getUser2(), room.getUser1()); // 通知玩家2
                return;
            }
        }
        // 7. 若又有玩家尝试加入房间，则提示报错
        result.setStatus(Constants.FAIL);
        result.setErrorMessage("当前房间已满！");
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(result)));
    }

    private void noticeGameReady(Room room, UserInfo user, UserInfo rivalUser) throws IOException {
        GameReadyResult resp = new GameReadyResult();
        resp.setRoomId(room.getRoomId());
        resp.setUserId(user.getUserId());
        resp.setRivalId(rivalUser.getUserId());
        resp.setWhiteUser(room.getWhiteUser());

        Result result = new Result();
        result.setStatus(Constants.GAME_READY);
        result.setErrorMessage("");
        result.setData(resp);
        // 将当前的响应数据传回给玩家
        WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(user.getUserId());
        webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(result)));
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 处理落子请求
        // 1. 获取用户身份信息
        UserInfo userInfo = (UserInfo) session.getAttributes().get(Constants.USER);
        // 处理用户尚未登录的情况
        if(userInfo == null) {
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(Result.noLogin())));
            log.info("[handleTextMessage] 当前玩家尚未登录");
            return;
        }
        // 2. 根据玩家 id 获取到房间对象
        Room room = roomManger.getRoomByUserId(userInfo.getUserId());
        // 处理用户未进入房间的情况
        if(room == null) {
            log.info("[handleTextMessage] 玩家：{}未进入房间", userInfo.getUserName());
            return;
        }
        // 3. 通过 room 对象来处理这次具体的请求
        room.putChess(message.getPayload());
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        UserInfo userInfo = (UserInfo) session.getAttributes().get(Constants.USER);
        if(userInfo == null) {
            return;
        }
        WebSocketSession loginSession = onlineUserManager.getFromGameRoom(userInfo.getUserId());
        // 避免多开情况下退出
        if(loginSession == session) {
            onlineUserManager.exitGameRoom(userInfo.getUserId());
            log.info("handleTransportError 玩家：" + userInfo.getUserName() + " 已离开游戏房间！");
        }

        // 通知对手获胜了
        noticeThatUserWin(userInfo);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        UserInfo userInfo = (UserInfo) session.getAttributes().get(Constants.USER);
        if(userInfo == null) {
            return;
        }
        WebSocketSession loginSession = onlineUserManager.getFromGameRoom(userInfo.getUserId());
        // 避免多开情况下退出
        if(loginSession == session) {
            onlineUserManager.exitGameRoom(userInfo.getUserId());
            log.info("afterConnectionClosed 玩家：" + userInfo.getUserName() + " 已离开游戏房间！");
        }
        // 通知对手获胜了
        noticeThatUserWin(userInfo);
    }

    private void noticeThatUserWin(UserInfo userInfo) throws IOException {
        // 根据当前玩家，找到玩家所在房间
        Room room = roomManger.getRoomByUserId(userInfo.getUserId());
        if(room == null) {
            // 房间已经释放，当前玩家没有对手了
            return;
        }
        // 找到对手
        UserInfo thatUser = (userInfo == room.getUser1()) ? room.getUser2() : room.getUser1();
        if(thatUser == null) {
            return;
        }
        log.info("当前玩家：{}，对手：{}", userInfo.getUserName(), thatUser.getUserName());
        // 判定对手的在线状态
        WebSocketSession thatSession = onlineUserManager.getFromGameRoom(thatUser.getUserId());
        if(thatSession == null) {
            // 对手也掉线了，无需通知对方
            return;
        }
        // 通知对手，你获胜了
        GameReadyResult resp = new GameReadyResult();
        resp.setRoomId(room.getRoomId());
        resp.setUserId(thatUser.getUserId());
        resp.setRivalId(userInfo.getUserId());
        resp.setWhiteUser(thatUser.getUserId());
        Result result = new Result();
        result.setStatus(Constants.PUT_CHESS);
        result.setErrorMessage("");
        result.setData(resp);
        thatSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(result)));
        log.info("通知玩家：{}胜利 {}",thatUser, result);
        // 更新玩家分数信息
        int winUserId = thatUser.getUserId();
        int loseUserId = userInfo.getUserId();
        userMapper.userWin(winUserId);
        userMapper.userLose(loseUserId);
        // 释放房间对象
        roomManger.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());
    }
}
